<?php

/**
 * RESTController provides a RESTful webservice implementation to Zend Framework
 * Controllers. This functionality is added by extending the RESTController
 * class with your application controllers.
 * 
 * By extending RESTController the action portion of the URL will be used as the
 * key (id, username, name, etc) of the resource or document and the action will
 * be index (list all resources of this type), view (see the resource by the
 * given key), create (create a new resource), update (update an existing
 * resource found by key), delete (delete resource by given key).
 * 
 * @author  Jacob Wright, jacwright@gmail.com
 * @version 0.1
 */

require_once 'Zend/Controller/Action.php';
require_once 'Zend/Filter/Inflector.php';

abstract class LayeredContentController extends Zend_Controller_Action
{
	
	/**
	 * The class name of the model this controller represents
	 *
	 * @var string
	 */
	protected $_modelName;
	
	/**
	 * The field name of the key used for lookup
	 *
	 * @var string
	 */
	protected $_keyName = 'id';
	
	/**
	 * The model type this controller handles
	 *
	 * @var Model
	 */
	protected $model;
	
	public function indexAction()
	{
		$this->view->items = $this->model->fetchAll()->toArray();
		
		if ($this->_helper->contextSwitch->getCurrentContext() == 'json')
			$this->_helper->json($this->view->items);
	}
	
	public function viewAction()
	{
		$key = $this->_getParam('key');
		$this->setItemByKey($key);
		
		if ($this->_helper->contextSwitch->getCurrentContext() == 'json')
			$this->_helper->json($this->item);
	}
	
	public function createAction()
	{
		$data = $this->_getParam('data');
		unset($data['submit']);
		$form = $this->model->getForm();
		
		if ($form->isValid($data)) {
			$key = $this->model->insert($data);
			$item = $this->setItemByKey($key);
		} else {
			throw new Exception("Form errors: " . $form->getErrorMessages());
		}
		
		if ($this->_helper->contextSwitch->getCurrentContext() == 'json')
			$this->_helper->json($item);
	}
	
	public function updateAction()
	{
		$key = $this->_getParam('key');
		
		if (!$key)
			return;
		
		$data = $this->_getParam('data');
		unset($data['submit']);
		$form = $this->model->getForm();
		
		if ($form->isValidPartial($data)) {
			$where = $this->model->getAdapter()->quoteInto($this->_keyName . ' = ?', $key);
			$this->model->update($data, $where);
			$item = $this->setItemByKey($key);
		} else {
			$this->view->form = $form;
    		$this->render('edit');
    		return;
		}
		
		if ($this->_helper->contextSwitch->getCurrentContext() == 'json')
			$this->_helper->json($this->item);
		// TODO fix this to redirect appropriatly
		elseif ($this->_helper->contextSwitch->getCurrentContext() == null)
			$this->_redirect('/lc/' . $this->_modelName . '/' . $item[$this->_keyName]);
	}
	
	public function deleteAction()
	{
		$key = $this->_getParam('key');
		$where = $this->model->getAdapter()->quoteInto($this->_keyName . ' = ?', $key);
		$this->model->delete($where);
	}
	
	
	protected function setItemByKey($key)
	{
		$select = $this->model->select()->where($this->_keyName . ' = ?', $key);
		list($this->view->item) = $this->model->fetchAll($select)->toArray();
		return $this->view->item;
	}
	
	
	/**
	 * Used to determine if this key is valid. Can be used to simply verify it
	 * is numerical, or a string of length 32, or a uuid, or can look up the key
	 * in the database even. The default is that anything (unless it is an
	 * existing action name) can be a key.
	 *
	 * @param string $key
	 * @return boolean
	 */
	protected function isValidKey($key)
	{
		return $key != null;
	}
	
	/**
	 * Used to format RESTful data. If your data is XML then you can return a
	 * SimpleXML object. If it is JSON, you can convert it here. 
	 *
	 * @param string $data
	 * @return unknown
	 */
	protected function formatData($data)
	{
		if ($this->_helper->contextSwitch->getCurrentContext() == 'json')
			$data = Zend_Json::decode($data);
		else
			parse_str($data, $data);
		
		return $data;
	}
	
	/**
	 * Disables the layout and optionally view so that one may use their own
	 * rendering mechanism.
	 */
	protected function disableLayout($viewToo = false)
	{
		if ($viewToo)
			$this->_helper->viewRenderer->setNoRender(true);
		if ($this->_helper->hasHelper('layout'))
			$this->_helper->layout->disableLayout();
	}
	

	public function init()
	{
		// setup context switching
		$contextSwitch = $this->_helper->getHelper('contextSwitch');
		$contextSwitch->addActionContext('index', array('json'));
		$contextSwitch->addActionContext('view', array('json'));
		$contextSwitch->addActionContext('create', array('json'));
		$contextSwitch->addActionContext('update', array('json'));
		$contextSwitch->initContext();
		
		// set the correct action for a RESTful request
		$this->setCorrectAction();
		
		// set up the model if it is defined
		if (!$this->_modelName)
			return;
		
		Zend_Loader::loadClass($this->_modelName);
		
		if (class_exists($this->_modelName))
			$this->model = new $this->_modelName();
	}
	
	
	/**
	 * Catches 
	 *
	 * @param string $name
	 * @param array $arguments
	 */
	protected function setCorrectAction()
	{
		$request = $this->getRequest();
		$key = $request->getParam('key');
		if ($key === null)
			$key = $request->getActionName();
		
		// if we have an action by this name already, this is an action, not key
		if ($key != 'index' && method_exists($this, $key . 'Action'))
			return;
		
		$params = $request->getParams();
		$action = $this->getAction($key);
		
		// An key of 'index' is actually null
		$key = ($key == 'index' ? null : $key);
		$params['key'] = $key;
		if ($action != 'index' && $action != 'create' && !$this->isValidKey($key))
			return;
		
		$request->setActionName($action);
		// refigure the context after changing the action
		$this->_helper->contextSwitch->initContext();
		
		// if this request is a create or update, get the data being posted
		if ($action == 'create' || $action == 'update')
			$params['data'] = $this->formatData($request->getRawBody());
		
		$request->setParams($params);
		
		// if the action is view, create, or update the template should be the
		// view template. If it is index, should be index, and delete should be
		// nothing. If using HTML, a redirect is best
		if ($action != 'index' && $action != 'delete')
			$this->_helper->viewRenderer('view');
		elseif ($action == 'delete')
			$this->disableLayout(true);
	}
	
	
	protected function getAction($key)
	{
		// set the default action first, then we'll see if there is another
		// specified
		$action = $key != 'index' ? 'view' : 'index';
		
		// change action if the HTTP method is used to specify the action 
		$method = $this->getRequest()->getMethod();
		if ($method == 'POST' && $key != 'index')
			$action = 'update';
		elseif ($method == 'POST')
			$action = 'create';
		elseif ($method == 'PUT')
			$action = 'update';
		elseif ($method == 'DELETE')
			$action = 'delete';
		
		// finally, change the action if it is set explicitly (creates, updates,
		// and deletes must always use a POST)
		if (isset($_GET['action']))
			$actionOverride = strtolower($_GET['action']);
		elseif (isset($_POST['action']))
			$actionOverride = strtolower($_POST['action']);
		
		if ($actionOverride && $method == 'POST') {
			$actionOverride = strtolower($_REQUEST['action']);
			if (	$actionOverride == 'create' ||
					$actionOverride == 'update' ||
					$actionOverride == 'delete')
			$action = $actionOverride;
		}
		
		return $action;
	}
}
